﻿using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using ChaterFuncLib;
using MySql.Data.MySqlClient;

namespace ChaterServer
{
    public class ChattingCore
    {
        // Сетевые переменные
        TcpListener listener;
        string host;
        uint port;
        public bool alive = false;
        private ArrayList Users;
        public uint Usercount;
        public MySqlConnection sqlconn;
        Timer acceptTimeout = new Timer();
        
        // Другое
        RichTextBox Log;
        delegate void LogHandler(string message, LogMessageType type, LogLevel level);
        int level;

        /// <summary>
        /// Конструктор класса ChattingCore
        /// </summary>
        /// <param name="log">Объект TextBox для логирования</param>
        public ChattingCore(RichTextBox log)
        {
            Log = log;
            Users = new ArrayList();
            acceptTimeout.Interval = 10;
            acceptTimeout.Tick += new EventHandler(Listen);

            if (!int.TryParse(Config.GetConfig("Logging", "LogLevel"), out level))
                level = 0;

            LogMessage("Добро пожаловать в графический сервер чатера на C# версии " + Service.ServerVersion, ChattingCore.LogMessageType.Normal, LogLevel.Standart);
            LogMessage("Для изменения настроек сервера щелкните по вкладке Сервер", ChattingCore.LogMessageType.Normal, LogLevel.Standart);
            LogMessage("Чтобы запустить сервер нажмите Меню->Запустить сервер или нажмите F2", ChattingCore.LogMessageType.Normal, LogLevel.Standart);
        }

        /// <summary>
        /// Запускает сервер
        /// </summary>
        public void StartServer()
        {
            if (alive)
            {
                LogMessage("Сервер уже запущен!", ChattingCore.LogMessageType.Error, LogLevel.Standart);
                return;
            }

            if (!File.Exists(".\\chater.ini"))
            {
                LogMessage("Файл chater.ini не существует!", ChattingCore.LogMessageType.Error, LogLevel.Standart);
                return;
            }

            LogMessage("Получение данных для прослушивания...", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            host = Config.GetConfig("Network", "Host");
            IPAddress ip;
            if (!IPAddress.TryParse(host, out ip))
            {
                LogMessage("В файле chater.ini указан некорректный IP-адрес!", ChattingCore.LogMessageType.Error, LogLevel.Standart);
                return;
            }
            port = uint.Parse(Config.GetConfig("Network", "Port"));
            if (!Service.isPort(port))
            {
                LogMessage("В файле chater.ini указан некорректный порт!", ChattingCore.LogMessageType.Error, LogLevel.Standart);
                return;
            }
            listener = new TcpListener(new IPEndPoint(ip, (int)port));
            LogMessage("Данные для прослушивания получены!", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            alive = true;
            GetMySQLData();
            LogMessage("Запускаю сервер на " + host + ":" + port.ToString(), ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            listener.AllowNatTraversal(true);
            listener.Start();
            LogMessage("Сервер запущен!", ChattingCore.LogMessageType.Normal, LogLevel.Standart);
            LogMessage("Начинаю прием подключений...", ChattingCore.LogMessageType.Normal, LogLevel.Debug);
            acceptTimeout.Start();

            Log.Parent.Controls["ServerStatus"].Text = "Статус сервера: запущен";
            Log.Parent.Controls["ListenHost"].Text = "Хост прослушивания: " + host;
            Log.Parent.Controls["ListenPort"].Text = "Порт прослушивания: " + port;
        }

        /// <summary>
        /// Запускает сервер
        /// </summary>
        /// <param name="ip">Параметр типа IPAddress, содержащий IP-адрес сервера</param>
        /// <param name="port">Порт сервера</param>
        public void StartServer(IPAddress ip, uint port)
        {
            if (alive)
            {
                LogMessage("Сервер уже запущен!", ChattingCore.LogMessageType.Error, LogLevel.Standart);
                return;
            }

            if (!File.Exists(".\\chater.ini"))
            {
                LogMessage("Файл chater.ini не существует!", ChattingCore.LogMessageType.Error, LogLevel.Standart);
                return;
            }

            host = ip.ToString();
            this.port = port;
            listener = new TcpListener(new IPEndPoint(ip, (int)port));
            alive = true;
            GetMySQLData();
            LogMessage("Запускаю сервер на " + host + ":" + port.ToString(), ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            listener.Start();
            LogMessage("Сервер запущен!", ChattingCore.LogMessageType.Normal, LogLevel.Standart);
            LogMessage("Начинаю прием подключений...", ChattingCore.LogMessageType.Normal, LogLevel.Debug);
            Usercount = 0;
            acceptTimeout.Start();

            Log.Parent.Controls["ServerStatus"].Text = "Статус сервера: запущен";
            Log.Parent.Controls["ListenHost"].Text = "Хост прослушивания: " + host;
            Log.Parent.Controls["ListenPort"].Text = "Порт прослушивания: " + port;
        }

        public void StartServer(IPAddress ip, uint port, IPAddress mysqlip, string db, string username, string password)
        {
            if (alive)
            {
                LogMessage("Сервер уже запущен!", ChattingCore.LogMessageType.Error, LogLevel.Standart);
                return;
            }

            host = ip.ToString();
            this.port = port;
            listener = new TcpListener(new IPEndPoint(ip, (int)port));
            alive = true;
            GetMySQLData(mysqlip, db, username, password);
            LogMessage("Запускаю сервер на " + host + ":" + port.ToString(), ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            listener.Start();
            LogMessage("Сервер запущен!", ChattingCore.LogMessageType.Normal, LogLevel.Standart);
            LogMessage("Начинаю прием подключений...", ChattingCore.LogMessageType.Normal, LogLevel.Debug);
            Usercount = 0;
            acceptTimeout.Start();

            Log.Parent.Controls["ServerStatus"].Text = "Статус сервера: запущен";
            Log.Parent.Controls["ListenHost"].Text = "Хост прослушивания: " + host;
            Log.Parent.Controls["ListenPort"].Text = "Порт прослушивания: " + port;
        }

        /// <summary>
        /// Останавливает сервер
        /// </summary>
        public void StopServer()
        {
            if (!alive)
            {
                LogMessage("Сервер еще не запущен!", ChattingCore.LogMessageType.Error, LogLevel.Standart);
                return;
            }

            LogMessage("Останавливаю сервер...", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            alive = false;
            acceptTimeout.Stop();
            listener.Stop();
            listener = null;
            Packet pkt = new Packet(0, PacketType.SMSG_DISCONNECT);
            foreach (Client i in Users)
            {
                try
                {
                    i.Stop();
                    i.SendMessage(pkt);
                    i.alive = false;
                    i.client.Close();
                } catch { }
            }
            Users.Clear();
            new MySqlCommand("UPDATE users SET online = 0;", sqlconn).ExecuteNonQuery();
            Usercount = 0;
            Users.RemoveRange(0, (int)Usercount);
            sqlconn.Close();
            LogMessage("Сервер остановлен!", ChattingCore.LogMessageType.Normal, LogLevel.Standart);

            Log.Parent.Controls["UserCount"].Text = "Количество пользователей: 0";
            Log.Parent.Controls["ServerStatus"].Text = "Статус сервера: остановлен";
            Log.Parent.Controls["ListenHost"].Text = "Хост прослушивания: не прослушивается";
            Log.Parent.Controls["ListenPort"].Text = "Порт прослушивания: не прослушивается";
            Log.Parent.Controls["MySQLHost"].Text = "MySQL хост: не подключено";
            Log.Parent.Controls["MySQLDB"].Text = "MySQL база данных: отсутствует";
            Log.Parent.Controls["MySQLUser"].Text = "MySQL пользователь: не указан";
            Log.Parent.Controls["MySQLPass"].Text = "MySQL пароль: не указан";
        }

        /// <summary>
        /// Получает настройки MySQL
        /// </summary>
        void GetMySQLData()
        {
            string sqlhost = string.Empty;
            string sqldb = string.Empty;
            string sqluser = string.Empty;
            string sqlpass = string.Empty;

            IPAddress ip;
            LogMessage("MySQL: начинаю получение данных!", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            sqlhost = Config.GetConfig("MySQL", "Host");
            if (!IPAddress.TryParse(sqlhost, out ip))
            {
                LogMessage("В файле chater.ini указан некорректный IP-адрес MySQL-сервера!", ChattingCore.LogMessageType.Error, LogLevel.Standart);
                StopServer();
                return;
            }
            sqldb = Config.GetConfig("MySQL", "Database");
            sqluser = Config.GetConfig("MySQL", "Username");
            sqlpass = Config.GetConfig("MySQL", "Password");
            sqlconn = new MySqlConnection("SERVER="
                + sqlhost
                + ";DATABASE="
                + sqldb
                + ";UID="
                + sqluser
                + ";PASSWORD="
                + sqlpass);
            LogMessage("MySQL: данные получены!", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            LogMessage("MySQL: тестирую соединение...", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            try
            {
                sqlconn.Open();
            }
            catch (MySqlException ex)
            {
                LogMessage("MySQL: невозможно подключиться к серверу, используя текущие данные! Текст ошибки: " + ex.Message, ChattingCore.LogMessageType.Error, LogLevel.Standart);
                StopServer();
                return;
            }

            string passtring = string.Empty;

            for (int i = 0; i < sqlpass.Length; i++)
                passtring += "*";

            LogMessage("MySQL: подключение успешно!", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            LogMessage("MySQL: подключено к " + sqlhost + ":" + "3306", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            LogMessage("MySQL: база данных " + sqldb, ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            LogMessage("MySQL: имя пользователя " + sqluser, ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            LogMessage("MySQL: пароль " + passtring, ChattingCore.LogMessageType.Normal, LogLevel.Extended);

            Log.Parent.Controls["MySQLHost"].Text = "MySQL хост: " + sqlhost;
            Log.Parent.Controls["MySQLDB"].Text = "MySQL база данных: " + sqldb;
            Log.Parent.Controls["MySQLUser"].Text = "MySQL пользователь: " + sqluser;
            Log.Parent.Controls["MySQLPass"].Text = "MySQL пароль: " + passtring;
        }

        public void GetMySQLData(IPAddress ip, string db, string username, string password)
        {
            sqlconn = new MySqlConnection("SERVER="
                + ip.ToString()
                + ";DATABASE="
                + db
                + ";UID="
                + username
                + ";PASSWORD="
                + password);
            LogMessage("Тестирую MySQL соединение...", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            try
            {
                sqlconn.Open();
            }
            catch (MySqlException ex)
            {
                LogMessage("Невозможно подключиться к серверу MySQL, используя текущие данные! Текст ошибки: " + ex.Message, ChattingCore.LogMessageType.Error, LogLevel.Standart);
                StopServer();
                return;
            }
            LogMessage("Подключение успешно!", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            string passtring = string.Empty;

            for (int i = 0; i < password.Length; i++)
                passtring += "*";

            LogMessage("MySQL: подключено к " + ip.ToString() + ":" + "3306", ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            LogMessage("MySQL: база данных " + db, ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            LogMessage("MySQL: имя пользователя " + username, ChattingCore.LogMessageType.Normal, LogLevel.Extended);
            LogMessage("MySQL: пароль " + passtring, ChattingCore.LogMessageType.Normal, LogLevel.Extended);

            Log.Parent.Controls["MySQLHost"].Text = "MySQL-хост: " + ip.ToString();
            Log.Parent.Controls["MySQLDB"].Text = "MySQL база данных: " + db;
            Log.Parent.Controls["MySQLUser"].Text = "MySQL пользователь: " + username;
            Log.Parent.Controls["MySQLPass"].Text = "MySQL пароль: " + passtring;
        }

        /// <summary>
        /// Функция добавления пользователя в список
        /// </summary>
        /// <param name="client">Пользователь типа Client</param>
        public void AddToUsers(Client client)
        {
            Users.Add(client);
            Usercount++;
            Log.Parent.Controls["UserCount"].Text = "Количество пользователей: " + Usercount;
        }

        public void RemoveFromUsers(Client client)
        {
            Users.Remove(client);
            Usercount--;
            Log.Parent.Controls["UserCount"].Text = "Количество пользователей: " + Usercount;
        }

        public void BroadcastMessage(string sender, string message)
        {
            foreach (Client i in Users)
                try
                {
                    i.SendMessage(sender, message);
                }
                catch { }
        }

        public void BroadcastPacket(Packet pkt)
        {
            foreach (Client i in Users)
                try
                {
                    i.SendMessage(pkt);
                }
                catch { }
        }

        /// <summary>
        /// Приниматель подключений
        /// </summary>
        private void Listen(object sender, EventArgs e)
        {
            if (listener != null)
                if (listener.Pending())
                    new Client(listener.AcceptTcpClient(), this);
        }

        /// <summary>
        /// Система логирования
        /// </summary>
        /// <param name="message">Сообщение лога</param>
        public void LogMessage(string message, LogMessageType type, LogLevel level)
        {
            string logmes = "[ " + DateTime.Now.ToString() + " ] " + message + "\n";

            LogHandler log = (mes, t, lvl) =>
                    {
                        if ((int)level > this.level)
                            return;
                        Log.AppendText(mes);
                        Log.Select(Log.TextLength - mes.Length, Log.TextLength);
                        switch (t)
                        {
                            case LogMessageType.Normal:
                                Log.SelectionColor = System.Drawing.Color.ForestGreen;
                                break;
                            case LogMessageType.Warning:
                                Log.SelectionColor = System.Drawing.Color.Orange;
                                break;
                            case LogMessageType.Error:
                                Log.SelectionColor = System.Drawing.Color.Red;
                                break;
                        }
                        Log.DeselectAll();
                        Log.SelectionStart = Log.TextLength;
                    };

            if (Log.InvokeRequired)
                Log.Invoke(log, new object[]{logmes, type, level});
            else
                log(logmes, type, level);
        }

        public enum LogMessageType
        {
            Normal,
            Warning,
            Error
        }
        public enum LogLevel
        {
            Standart,
            Extended,
            Debug
        }
    }
}